/******************************************************************************
	This file is a part of the Snapshot Bioinformatics Project

	Copyright (C) 2012-2013 Jarom Schow

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/

#include "WorkbenchUI.h"

#include <QtCore/QTime>
#include <QtCore/QEvent>

#include <QtWidgets/QApplication>
#include <QtWidgets/QFileDialog>
#include <QtWidgets/QLabel>
#include <QtWidgets/QMenu>
#include <QtWidgets/QMenuBar>
#include <QtWidgets/QMessageBox>
#include <QtWidgets/QStatusBar>
#include <QtWidgets/QTabWidget>

#include <QtSql/QSqlError>

#include "Core.h"
#include "GenBankParser.h"
#include "SeqDatabase.h"
#include "SqlServer.h"
#include "FileUtil.h"
#include "GzFile.h"
#include "Progress.h"

#include "AppSettings.h"
#include "ConnectDlg.h"
#include "GenBankDlg.h"
#include "Workbench.h"
#include "WorkbenchWgt.h"

const int	c_defaultWidth = 400;
const int	c_defaultHeight = 400;

/**
**/
WorkbenchUI::WorkbenchUI() : m_server( NULL )
{
	setWindowTitle( qApp->applicationName() + " " + qApp->applicationVersion() );

	buildMainMenu();

	m_benchWgt = new WorkbenchWgt( this );
	setCentralWidget( m_benchWgt );
}

/**
**/
WorkbenchUI::~WorkbenchUI()
{
}

/**
**/
bool WorkbenchUI::event( QEvent *e )
{
	if( e->type() != QEvent::User )
		return QMainWindow::event( e );

	Bio::SqlServerSettings	settings;

	AppSettings::loadDBSettings( settings );

	if( !settings.isValid() ) {
		connectToDB();
		return true;
	}

	if( !connectToDB( settings ) )
		connectToDB();

	return true;
}

/**
**/
void WorkbenchUI::closeEvent( QCloseEvent *e )
{
	saveUI();
	QMainWindow::closeEvent( e );
}

/**
**/
void WorkbenchUI::connectToDB()
{
	Bio::SqlServerSettings	settings;

	AppSettings::loadDBSettings( settings );

	ConnectDlg	dlg( this, settings );

	if( dlg.exec() != QDialog::Accepted )
		return;

	settings = dlg.getServerSettings();

	if( !connectToDB( settings ) )
		return;

	AppSettings::saveDBSettings( settings );
}

/**
**/
void WorkbenchUI::createDB()
{
	Bio::SqlServerSettings	settings;

	AppSettings::loadDBSettings( settings );

	ConnectDlg	dlg( this, settings, true );

	if( dlg.exec() != QDialog::Accepted )
		return;

	settings = dlg.getServerSettings();

	if( !createDB( settings ) )
		return;

	AppSettings::saveDBSettings( settings );
}

/**
**/
void WorkbenchUI::loadSeqFiles()
{
	QStringList	filenames = QFileDialog::getOpenFileNames( this, "Select one or more sequence files to load", "D:/Seq/GenBank" );

	if( filenames.isEmpty() )
		return;

	int				idx = 1;
	bool			status = true;
	QTime			timer;
	Bio::Progress	topLevel( "Loading sequence files...", 0, true );
	Bio::Progress	progress( "", filenames.count() );

	timer.start();

	QStringListIterator	fileIt( filenames );

	while( fileIt.hasNext() ){
		QString	filename = fileIt.next();

		progress.status( QString( "Loading file %1 (%2/%3)..." ).arg( Bio::FileUtil::getFilename( filename ) )
			.arg( idx++ ).arg( filenames.count() ) );
		if( !loadSeqFile( filename ) )
			status = false;
		progress.step();
		if( progress.isCanceled() ){
			Bio::Core::log( "Sequence file load cancelled by user." );
			status = false;
			break;
		}
	}

	progress.finish();

	int elapsed = timer.elapsed() / 100;

	if( status ){
		Bio::Core::log( QString( "%1 file(s) loaded in %2 min %3.%4 sec." ).arg( filenames.count() )
			.arg( elapsed / 600 ).arg( (elapsed % 600) / 10 ).arg( elapsed % 10 ) );

		QMessageBox::information( this, "Finished loading records", QString( "Loaded %1 file(s) in %2 min %3.%4 sec." )
			.arg( filenames.count() ).arg( elapsed / 600 ).arg( (elapsed % 600) / 10 ).arg( elapsed % 10 ) );
	}
	else {
		QMessageBox::critical( this, "Error loading records", "One or more database errors occurred, see the log file for details." );
		Bio::Core::log( "Errors occurred while loading files." );
	}
}

/**
**/
void WorkbenchUI::loadGenBank()
{
	Bio::GenBank	genBank;
	Bio::Progress	progress( "Connecting to GenBank...", -1 );

	if( !genBank.connectToGenBank() ){
		QMessageBox::critical( this, "Load sequences from GenBank", "Failed to connect to the GenBank ftp. "
			"Check the log file for details." );
		return;
	}

	progress.finish();

	GenBankDlg	dlg( this, &genBank );

	if( dlg.exec() != QDialog::Accepted )
		return;
}

/**
**/
void WorkbenchUI::viewLogFile()
{
	Bio::Core::flushLog();
	g_bench->showURL( QString( "file://%1/log.txt" ).arg( g_bench->getAppDataPath() ) );
}

/**
**/
void WorkbenchUI::testProgress()
{
	int			nOuter = 12;
	int			nInner = 10;

	Bio::Progress	outer( "Outer progress", nOuter );

	for( int i = 0; i < nOuter; i++ ){
		Bio::Progress	inner( "Inner progress", nInner );

		for( int j = 0; j < nInner; j++ ){
			QTime dieTime = QTime::currentTime().addMSecs( 100 );
			while( QTime::currentTime() < dieTime )
				QCoreApplication::processEvents( QEventLoop::AllEvents, 100 );
			inner.step();
		}
		outer.step();
	}
}

/**
**/
void WorkbenchUI::updateTaxonomy()
{
	if( !m_server ){
		QMessageBox::critical( this, "Update Taxonomy", "You must be connected to a database to update the taxonomy." );
		return;
	}

	Bio::Progress	progress( "Updating Taxonomy...", 0, true );

	bool	status = m_server->updateTaxonomy( g_bench->getAppDataPath() );

	progress.finish();

	if( !status )
		QMessageBox::critical( this, "Update Taxonomy", "One or more errors occurred while performing update. "
			"See the log file for details." );
}

/**
**/
void WorkbenchUI::init()
{
	restoreUI();
	qApp->postEvent( this, new QEvent( QEvent::User ) );
}

/**
**/
void WorkbenchUI::buildMainMenu()
{
	QMenuBar	*bar = menuBar();
	QMenu		*menu;

	menu = menuBar()->addMenu( tr( "&File" ) );
	menu->addAction( tr( "Connect to Database..." ), this, SLOT(connectToDB()) );
	menu->addAction( tr( "Create Database..." ), this, SLOT(createDB()) );
	menu->addSeparator();
	menu->addAction( tr( "Load sequences from file..." ), this, SLOT(loadSeqFiles()) );
	menu->addAction( tr( "Load sequences from GenBank..." ), this, SLOT(loadGenBank()) );
	menu->addAction( tr( "Update Taxonomy" ), this, SLOT(updateTaxonomy()) );
	menu->addSeparator();
	menu->addAction( tr( "Exit" ), this, SLOT(close()) );

	menu = bar->addMenu( tr( "&Help" ) );
	menu->addAction( tr( "View log file..." ), this, SLOT(viewLogFile()) );
	menu->addSeparator();
	menu->addAction( tr( "Test Progress" ), this, SLOT(testProgress()) );

	m_dbStatusLbl = new QLabel( "Not Connected" );
	statusBar()->addPermanentWidget( m_dbStatusLbl );
}

/**
**/
void WorkbenchUI::restoreUI()
{
	bool		maximized;
	QSettings	settings;

	settings.beginGroup( "UserInterface" );

	maximized = settings.value( "Maximized", false ).toBool();
	if( maximized )
		showMaximized();

	int	width, height, left, top;

	width = settings.value( "Width", c_defaultWidth ).toInt();
	height = settings.value( "Height", c_defaultHeight ).toInt();
	left = settings.value( "Left", -1 ).toInt();
	top = settings.value( "Top", -1 ).toInt();

	if( left < 0 || top < 0 )
		resize( width, height );
	else
		setGeometry( left, top, width, height );
}

/**
**/
void WorkbenchUI::saveUI()
{
	QRect		geom = geometry();
	QSettings	settings;

	settings.beginGroup( "UserInterface" );

	settings.setValue( "Maximized", isMaximized() );
	settings.setValue( "Width", geom.width() );
	settings.setValue( "Height", geom.height() );
	settings.setValue( "Left", geom.left() );
	settings.setValue( "Top", geom.top() );
}

/**
**/
bool WorkbenchUI::connectToDB( const Bio::SqlServerSettings &settings )
{
	delete m_server;
	m_server = NULL;

	m_server = new Bio::SqlServer( settings );

	if( !m_server->isConnected() ){
		delete m_server;
		m_server = NULL;
		return false;
	}

	m_dbStatusLbl->setText( QString( "%1 on %2@%3:%4" ).arg( settings.getDatabaseName() )
		.arg( settings.getUserName() ).arg( settings.getHostName() ).arg( settings.getPort() ) );
	m_benchWgt->setServer( m_server );

	return true;
}

/**
**/
bool WorkbenchUI::createDB( const Bio::SqlServerSettings &settings )
{
	delete m_server;
	m_server = NULL;

	m_server = new Bio::SqlServer;

	if( !m_server->create( settings ) ){
		delete m_server;
		m_server = NULL;
		m_dbStatusLbl->setText( "Not Connected" );
		return false;
	}

	m_dbStatusLbl->setText( QString( "%1 on %2@%3:%4" ).arg( settings.getDatabaseName() )
		.arg( settings.getUserName() ).arg( settings.getHostName() ).arg( settings.getPort() ) );
	m_benchWgt->setServer( m_server );

	return true;
}

/**
**/
bool WorkbenchUI::loadSeqFile( const QString &filename )
{
	bool				status = true;
	Bio::GenBankParser	parser;
	QTime				timer;

	Bio::Core::log( "Reading sequence file " + filename );

	timer.start();

	if( filename.right( 3 ).toLower() == ".gz" ){
		Bio::GzFile	file( filename );
		if( !file.open( QIODevice::ReadOnly ) ){
			QString	msg = QString( "Could not open file '%1' for reading." ).arg( filename );
			QMessageBox::critical( this, "Error opening file", msg );
			Bio::Core::log( msg );
			return false;
		}
		status = parser.parse( file );
		file.close();
	}
	else {
		QFile	file( filename );
		if( !file.open( QIODevice::ReadOnly ) ){
			QString	msg = QString( "Could not open file '%1' for reading." ).arg( filename );
			QMessageBox::critical( this, "Error opening file", msg );
			Bio::Core::log( msg );
			return false;
		}
		status = parser.parse( file );
		file.close();
	}

	int elapsed1 = timer.elapsed() / 100;
	int	nRecords = parser.getNumRecords();

	if( status ){
		Bio::Core::log( QString( "%1 records read from file in %2 min %3.%4 sec." ).arg( nRecords )
			.arg( elapsed1 / 600 ).arg( (elapsed1 % 600) / 10 ).arg( elapsed1 % 10 ) );
	}
	else {
		QMessageBox::critical( this, "Error reading file", "One or more parser errors occurred, see the log file for details." );
		Bio::Core::log( "Error reading file." );
		return false;
	}

	Bio::SeqDatabase	db = m_server->getSeqDatabase( "GenBank" );

	if( !db.isValid() ){
		QMessageBox::critical( this, "Error loading records", "The sequence database is not valid." );
		Bio::Core::log( "Error loading records: the sequence database is not valid." );
		return false;
	}

	Bio::Core::log( "Loading records into database..." );

	timer.restart();

	status = db.load( &parser );
	if( !status ){
		//QMessageBox::critical( this, "Error loading records", "One or more database errors occurred, see the log file for details." );
		Bio::Core::log( "Error loading records into database." );
		return false;
	}

	int	elapsed2 = timer.elapsed() / 100;

	Bio::Core::log( QString( "Loaded %1 records into database in %2 min %3.%4 sec." ).arg( nRecords )
		.arg( elapsed2 / 600 ).arg( (elapsed2 % 600) / 10 ).arg( elapsed2 % 10 ) );

	return true;
}
